Using GL_ARB_multitexture and GL_EXT_vertex_array OpenGL extensions.
Note: for understanding this material it is necessary to know foundations of OOP in C++, foundations of OpenGL programming. Also you need to know foundations of 3D graphics and mathematic. Attention!!! In this article I continue using my VXFile class, so you must read at least Part I of my previous article ("Own 3D file format or Exporter plug-in for 3DS Max").
Part I
Intro: What is OpenGL extension
OpenGL is one of the basic and famous graphical APIs developed by SGI. Many famous games and programs use OpenGL for rendering. OpenGL is Open Graphics Library - "a software interface to graphics hardware", as written in the specification. This library is a set of commands which the programmer can use for rendering. I don't want to tell you what OpenGL is, because you can read it yourself in the specification. I want to help you understand what OpenGL extension is and how it works.
So, OpenGL extension is a hardware-supported procedure which you can use from your program. Extensions are used in OpenGL v1.1 and higher. But they can be different for different video-cards. Standard OpenGL API is placed in OpenGL32.dll (in win32 OS). But the interface for using extensions is provided in another DLL. That depends on your video card driver. For my "S3 Pro Savage DDR" video card it is nbicdnt.dll. For other video adapters it can be something else. There is an interface for using OpenGL extensions in it. DirectX (Direct3D) also uses video card extensions, but they are named differently in it and are called "Caps". For example the OpenGL extension for bump-mapping GL_ARB_texture_env_dot3 is called D3DTEXOPCAPS_DOTPRODUCT3 in DirectX. So OpenGL and DirectX use the same hardware procedures, but coding for them is very different.
So, how can I use video card extensions in OpenGL? First of all I must include "glext.h". It has all declarations for using extensions. To know what extensions are supported by your video card you may use the glGetString(GL_EXTENSIONS) function. So let's write a simple function for determining the extensions of your video card.
Listing 1: Extension determiner function (VC++)
#include "glext.h"
bool HaveExtension ( const char * ext_name )
{
const char * ext_string = (const char *)glGetString ( GL_EXTENSIONS );
const char * start = ext_string;
const char * ptr;
while ( ( ptr = strstr ( start, ext_name ) ) != NULL )
{
// we've found, ensure name is exactly ext
const char * end = ptr + strlen ( ext );
if ( isspace ( *end ) || *end == '\0' )
return true;
start = end;
}
return false;
}
Using this function you may ensure what extensions are supported. Call it with the name of extension, like bool isMultiTextring = HaveExtension("GL_ARB_multitexture");
But how can I use the extension? It's very simple. After including "glext.h" you may use wglGetProcAddress(LPCSTR your_gl_extension_function_name) from "wingdi.h". All prototypes of extensions functions are described in "glext.h".
For example:
PFNGLACTIVETEXTUREARBPROC glActiveTextureARB = NULL;
...
glActiveTextureARB=(PFNGLACTIVETEXTUREARBPROC)wglGetProcAddress("glActiveTextureARB");
Let's examine this code. PFNGLACTIVETEXTUREARBPROC is a prototype of the glActiveTextureARB function. So we declared a variable of the glActiveTextureARB function and then got its address by the wglGetProcAddress function. Well, you must check up if the required extension is supported, else the program will throw an exception like "Unhandled exception bla bla bla ...".
Chapter I: GL_ARB_multitexture
What is the GL_ARB_multitexture extension? GL_ARB_multitexture means that the video adapter supports more than one texture mapping. To know how many textures are supported you may use the function glGetIntegerv.
For example:
int maxTextureUnits;
glGetIntegerv ( GL_MAX_TEXTURE_UNITS_ARB, &maxTextureUnits );
There are different types of textures blending for it. The basic ones are GL_REPLACE, GL_DECAL, GL_MODULATE and GL_BLEND. They don't have any parameters. That is an example of using multitexture extension:
Listing 1: Using multitexture extension
//Here are our function variables
PFNGLACTIVETEXTUREARBPROC glActiveTextureARB = NULL;
PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB = NULL;
PFNGLMULTITEXCOORD1FARBPROC glMultiTexCoord1f = NULL;
PFNGLMULTITEXCOORD1FVARBPROC glMultiTexCoord1fv = NULL;
PFNGLMULTITEXCOORD2FARBPROC glMultiTexCoord2f = NULL;
PFNGLMULTITEXCOORD2FVARBPROC glMultiTexCoord2fv = NULL;
//procedure for initializing multitexturing
bool initMultitexture ()
{
glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC) wglGetProcAddress ( "glActiveTextureARB" );
glClientActiveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC) wglGetProcAddress ( "glClientActiveTextureARB" );
glMultiTexCoord1f = (PFNGLMULTITEXCOORD1FARBPROC) wglGetProcAddress ( "glMultiTexCoord1fARB" );
glMultiTexCoord1fv = (PFNGLMULTITEXCOORD1FVARBPROC) wglGetProcAddress ( "glMultiTexCoord1fvARB" );
glMultiTexCoord2f = (PFNGLMULTITEXCOORD2FARBPROC) wglGetProcAddress ( "glMultiTexCoord2fARB" );
glMultiTexCoord2fv = (PFNGLMULTITEXCOORD2FVARBPROC) wglGetProcAddress ( "glMultiTexCoord2fvARB" );
return glActiveTextureARB != NULL &&
glClientActiveTextureARB != NULL &&
glMultiTexCoord1f != NULL &&
glMultiTexCoord1fv != NULL &&
glMultiTexCoord2f != NULL &&
glMultiTexCoord2fv != NULL;
}
void Init1st()
{
//set first texture
glActiveTextureARB ( GL_TEXTURE0_ARB );
glEnable ( GL_TEXTURE_2D );
glBindTexture ( GL_TEXTURE_2D, tex_id1 ); //bind texture image
glTexEnvi ( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL );
}
void Init2nd()
{
//set second texture
glActiveTextureARB ( GL_TEXTURE1_ARB );
glEnable ( GL_TEXTURE_2D );
glBindTexture ( GL_TEXTURE_2D, text_id2 ); //bind texture image
glTexEnvi ( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD);
//GL_ADD blending method
}
void RenderObject()
{
...
Init1st();
Init2nd();
glBegin(GL_TRIANGLES);
...
glNormal3f(n.x, n.y, n.z); //normal for this vertex
glMultiTexCoord2f(GL_TEXTURE0_ARB, t1.u, t1.v );//first texture coordinates
glMultiTexCoord2f(GL_TEXTURE1_ARB, t2.u, t2.v );//second texture coordinates
glVertex3f(v.x, v.y, v.z); //vertex
...
glEnd();
}
That is all! But this is only a simple texture blending method. For more complex texture blending you need to use the GL_EXT_texture_env_combine extension. It doesn't add any function, but it adds a new state of texture mapping GL_COMBINE_ARB which may be turned on by the glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_COMBINE_ARB) command. But I think texture blending is a very big topic and it is for another article, so let's stop at this.
Chapter II: GL_EXT_vertex_array and GL_EXT_compiled_vertex_array
The GL_EXT_vertex_array extension is required for buffer rendering. This is an extension of standard OpenGL glDrawArrays, glNormalPointer, glVertexPointer, glTexCoordPointer and glEnableClientState buffer working functions. Using the GL_EXT_vertex_array extension the render process to become faster, because it is done by hardware. glEnableClientState is necessary for enabling buffers of vertexes, texture coordinates and normals. glNormalPointer, glVertexPointer, glTexCoordPointer are used for posting normals, vertexes and texture coordinates buffers accordingly. glDrawArrays is for rendering all data from the buffers. GL_EXT_vertex_array gives access to the next functions:
1. glDrawArraysEXT is for rendering all buffers
2. glVertexPointerEXT is for setting pointer to vertex buffer
3. glNormalPointerEXT is for setting pointer to normal buffer
4. glTexCoordPointerEXT is for setting pointer to texcoords buffer
GL_EXT_compiled_vertex_array gives access to the following functions:
1. glLockArraysEXT is for locking buffer for changing it
2. glUnlockArraysEXT is for unlocking buffer
I show you code better trying to explain all of this stuff.
Listing 2: Using GL_EXT_vertex_array and GL_EXT_compiled_vertex_array extension
//Vertex arrays
PFNGLDRAWARRAYSEXTPROC glDrawArraysEXT = NULL;
PFNGLVERTEXPOINTEREXTPROC glVertexPointerEXT = NULL;
PFNGLNORMALPOINTEREXTPROC glNormalPointerEXT = NULL;
PFNGLTEXCOORDPOINTEREXTPROC glTexCoordPointerEXT = NULL;
//Compiled arrays
PFNGLLOCKARRAYSEXTPROC glLockArraysEXT = NULL;
PFNGLUNLOCKARRAYSEXTPROC glUnlockArraysEXT = NULL;
bool initArrays ()
{
glDrawArraysEXT = (PFNGLDRAWARRAYSEXTPROC) wglGetProcAddress ( "glDrawArraysEXT" );
glVertexPointerEXT = (PFNGLVERTEXPOINTEREXTPROC) wglGetProcAddress ( "glVertexPointerEXT" );
glNormalPointerEXT = (PFNGLNORMALPOINTEREXTPROC) wglGetProcAddress ( "glNormalPointerEXT" );
glTexCoordPointerEXT= (PFNGLTEXCOORDPOINTEREXTPROC) wglGetProcAddress ( "glTexCoordPointerEXT" );
glLockArraysEXT = (PFNGLLOCKARRAYSEXTPROC) wglGetProcAddress ( "glLockArraysEXT" );
glUnlockArraysEXT = (PFNGLUNLOCKARRAYSEXTPROC) wglGetProcAddress ( "glUnlockArraysEXT" );
glEnableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
return glDrawArraysEXT != NULL
&& glVertexPointerEXT != NULL
&& glNormalPointerEXT != NULL
&& glTexCoordPointerEXT!= NULL;
}
...
VXFile scn; //that is class from my previous article, you may use your own structure
...
static VXRecord * pv;
void LoadObject()
{
if (initArrays()) scn.LoadVXFile("object1.vxa");
}
void PrepareObject(int i)
{
glLockArraysEXT( 0, scn.Head.numRecords ); //Locking our array data for changing
pv = &scn.Records[scn.Objects[i].Offset]; //getting pointer to vertex data
//setting normal pointer
glNormalPointerEXT(GL_FLOAT,sizeof(VXRecord),scn.Objects[i].numRecords,pv[0].n);
//setting vertex pointer
glVertexPointerEXT(3,GL_FLOAT,sizeof(VXRecord),scn.Objects[i].numRecords,pv[0].v);
//setting texture coordinates pointer
glTexCoordPointerEXT(2,GL_FLOAT,sizeof(VXTexCoord),scn.Objects[i].numRecords, scn.Objects[i].TexCoords[0].uv);
//unlocking our buffer
glUnlockArraysEXT();
//draw our buffers
glDrawArraysEXT (GL_TRIANGLES, 0, scn.Objects[i].numRecords);
}
void RenderScene()
{
...
for (i=0; i<scn.Head.numObjects; i++)
{
PrepareObject(i);
}
...
}
I think there isn't any complex code. Note that I used the VXFile class from my previous article, so I didn't describe it here.
Chapter III: Using multitexturing and vertex arrays together
When I wanted to use multitexturing and vertex arrays together I didn't know how to do it, because GL_ARB_multitexture doesn't give a procedure for setting texture coordinates array. It gives only glMultiTexCoord2f. So I decided to explore this problem by myself. I found that I can use same glTexCoordPointer or glTexCoordPointerEXT, and for switching textures I must do with the same glActiveTextureARB. Look at the code below:
Listing 3: Using multitexturing and vertex arrays together
...
glClientActiveTextureARB(GL_TEXTURE0_ARB);
glTexCoordPointerEXT(2,GL_FLOAT,sizeof(VXTexCoord),scn.Objects[i].numRecords, scn.Objects[i].TexCoords[0].uv);
glClientActiveTextureARB(GL_TEXTURE1_ARB);
glTexCoordPointerEXT(2,GL_FLOAT,sizeof(VXTexCoord),scn.Objects[i].numRecords, scn.Objects[i].TexCoords[0].uv2);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glClientActiveTextureARB(GL_TEXTURE0_ARB);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glClientActiveTextureARB(GL_TEXTURE1_ARB);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, tex_id1 );
glActiveTextureARB(GL_TEXTURE1_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, tex_id2 );
...
glDrawArraysEXT (GL_TRIANGLES, 0, scn.Objects[i].numRecords);
That is all for now. I tried to explain all for newbies, but I think it may be useful for an experts. For any questions/comments/bug reports/opinions contact me.